/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.compiler.checker.type;


import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import dex.compiler.model.definition.ClassDef;
import dex.compiler.model.definition.UserType;
import dex.compiler.model.program.NameSearcher;
import dex.compiler.model.program.Path;
import dex.compiler.model.program.Programs;
import dex.compiler.problem.Reporter;


/**
 *  A class hierarchy.
 */
class Hierarchy {


	/**
	 * The root of all classes.
	 */
	final public static String ROOT = "dex:core:Object";


	/**
	 * Maps fully-qualified subclass name to fully-qualified superclass name.
	 */
	private Map<String,String> classes;

	
	/**
	 * Reporter used to report errors.
	 */
	private Reporter reporter;

	
	/**
	 * Constructs a new hierarchy.
	 * 
	 * @param config  the configuration to use
	 */
	public Hierarchy(Reporter reporter) {
		this.classes = new HashMap<String,String>();
		classes.put(ROOT, ROOT);
		this.reporter = reporter;
	}


	/**
	 * Determines the superclass of the given class.
	 * 
	 * @param cls  the path to the class whose superclass to return
	 * @return  that class's superclass
	 */
	public String determineSuperclass(Path cls) {
		if (!(cls.getDefinition() instanceof ClassDef)) {
			throw new IllegalArgumentException("cls must point to a ClassDef.");
		}
		String className = cls.qualified();
		String superclass = classes.get(className);
		if (superclass != null) {
			return superclass;
		}
		return determineSuperclass(new HashSet<String>(), cls);
	}
	
	
	/**
	 * Recursively looks up superclasses.  For instance, if Baz
	 * extends Bar, and Bar extends Foo, and Foo extends object,
	 * and if you then invoke determineSuperclass(Baz), then
	 * the hierarchy will have information about Baz, Bar and Foo
	 * as well.  
	 * 
	 * @param pending  set of previously discovered superclasses
	 * @param path     the path to the class to check
	 * @return
	 */
	private String determineSuperclass(Set<String> pending, Path path) {
		String className = path.qualified();
		if (className.equals(ROOT)) {
			return ROOT;
		}
		if (!(path.getDefinition() instanceof ClassDef)) {
			reporter.error(TypeProblems.BAD_SUPERCLASS, path.getDefinition().getPlace(), path.qualified());
			classes.put(className, ROOT);
			return ROOT;
		}
		if (!pending.add(className)) {
			reporter.error(TypeProblems.CIRCULAR_CLASS, path.getDefinition().getPlace(), "Circular class dependency.");
			classes.put(className, ROOT);
			return ROOT;
		}
		ClassDef classDef = (ClassDef)path.getDefinition();
		
		String sclass = classDef.getSuperclass();
		NameSearcher searcher = new NameSearcher(UserType.class, sclass);
		List<Path> found = Programs.search(path.getProgram(), path.getUnit(), searcher);
		int size = found.size();
		if (size == 0) {
			reporter.error(TypeProblems.MISSING_SUPERCLASS, classDef.getPlace(), sclass);
			classes.put(className, ROOT);
			return ROOT;
		}
		if (size > 1) {
			reporter.error(TypeProblems.AMBIGUOUS_SUPERCLASS, classDef.getPlace(), sclass, found);
			classes.put(className, ROOT);
			return ROOT;
		}
		
		Path superPath = found.get(0);
		if (!Programs.checkVisibility(path.getUnit(), superPath)) {
			reporter.error(TypeProblems.INVISIBLE_SUPERCLASS, classDef.getPlace(), superPath.qualified());
			classes.put(className, ROOT);
			return ROOT;
		}

		determineSuperclass(pending, found.get(0));
		String superName = found.get(0).qualified();
		classes.put(className, superName);
		
		return superName;
	}


	/**
	 * Returns true if cls is a subclass of superclass.
	 * 
	 * @param cls  the class to check 
	 * @param superclass  the superclass to check against
	 * @return  true if cls is a sublcass of superclass
	 */
	public boolean isSubclass(String cls, String superclass) {
		if (superclass == ROOT) {
			return true;
		}
		if (cls == ROOT) {
			return false;
		}
		if (cls.equals(superclass)) {
			return true;
		}
		while ((cls != ROOT) && (cls != null)) {
			System.out.println(cls + " vs " + superclass);
			cls = classes.get(cls);
			if (cls.equals(superclass)) {
				return true;
			}
		}
		return false;
	}

}
